﻿#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

//自定义类型：结构体，枚举，联合

//结构体
/*struct Stu
{
	char name[20];
	int age;
}s1, s2;*///全局变量，s1,s2是两个结构体变量

//typedef struct Stu
//{
//	char name[20];
//	int age;
//}Stu;//此时的Stu则是结构体类型重命名后的名字
//
//typedef struct Stu Stu;//也可以在这里重命名
//
//int main()
//{
//	struct Stu s3, s4;//局部变量
//	Stu s5, s6;
//	return 0;
//}

//特殊声明
//在声明结构的时候，可以不完全的声明

//匿名结构体类型(只能使用一次)
//下面两个结构体在声明的时候省略掉了结构体标签（tag）
//struct
//{
//	int a;
//	char b;
//	float c;
//}x;
//struct
//{
//	int a;
//	char b;
//	float c;
//}*p;
//int main()
//{
//	//在上面代码的基础上，下面代码合法吗？
//	p = &x;//error，编译器会把上面两个声明当成完成不同的两个类型，所以是非法的
//	       //匿名的结构体类型，如果没有对结构体类型重命名的话，基本上只能使⽤⼀次。
//
//	return 0;
//}

//结构体自引用
//在结构中包含⼀个类型为该结构本⾝的成员是否可以呢？
//比如，定义⼀个链表的节点：
//struct Node
//{
//	int data;
//	struct Node next;//这样是不行的，因为⼀个结构体中再包含⼀个同类型的结构体变量，这样结构体变量的⼤⼩就会⽆穷的⼤，是不合理的。
//};
////正确的做法是使用指针
//struct Node
//{
//	int data;
//	struct Node* next;//指向下一个节点的地址
//	//结构体在编译时需要确定大小，直接包含自身会导致无限递归，无法确定内存大小；使用指针（固定大小，指针大小4/8字节）
//	//就可以确定大小（使用指针自引用是唯一可行方案）
//};

//在结构体⾃引⽤使⽤的过程中，夹杂了 typedef 对匿名结构体类型重命名，也容易引⼊问题，看看下⾯的代码，可⾏吗？
//typedef struct
//{
//	int data;
//	Node* next;
//}Node;
//答案是不⾏的，因为Node是对前⾯的匿名结构体类型的重命名产⽣的，但是在匿名结构体内部提前使⽤Node类型来创建成员变量，这是不⾏的。
//解决⽅案如下：定义结构体不要使⽤匿名结构体了
//typedef struct Node
//{
//	int data;
//	struct Node* next;
//}Node;
//int main()
//{
//	struct Node n1;
//	struct Node n2;
//	n1.next = &n2;
//
//	return 0;
//}

//结构体变量的定义和初始化
//struct Point  //类型声明
//{
//	int x;
//	int y;
//}p1 = { 10,20 };//声明类型的同时定义变量p1并初始化
//
//struct Point p2 = { 0,0 };
//
//struct S
//{
//	int num;
//	char ch;
//	struct Point p;
//	float d;
//};
//
//int main()
//{
//	struct Point p3 = { 1,2 };
//	struct S s = { 100,'w',{2,5},3.14f };//结构体嵌套初始化
//	struct S s2 = { .d = 1.2f,.p.x = 3,.p.y = 5,.ch = 'q',.num = 200 };//也可以乱序
//	printf("%d %c %d %d %f\n", s.num, s.ch, s.p.x, s.p.y, s.d);
//	printf("%d %c %d %d %f\n", s2.num, s2.ch, s2.p.x, s2.p.y, s2.d);
//
//	return 0;
//}

//结构体内存对齐
//讨论:计算结构体的⼤⼩
#include <stddef.h>
//struct S1
//{
//	char c1;
//	int i;
//	char c2;
//};
//
//struct S2
//{
//	char c1;
//	char c2;
//	int i;
//};
//
//int main()
//{
//	//printf("%d\n", sizeof(struct S1));//12
//	//printf("%d\n", sizeof(struct S2));//8
//	printf("%d\n", offsetof(struct S1, c1));//0
//	printf("%d\n", offsetof(struct S1, i));//4       -----> 到0偏移的距离
//	printf("%d\n", offsetof(struct S1, c2));//8
//
//	return 0;
//}
//结构体大小的计算并不是简单的成员类型大小的相加，而是有自己的计算方式规则 --- 内存对齐

//修改默认对齐数（VS中，默认对齐数是8）
//#pragma 这个预处理指令，可以改变编译器的默认对⻬数。
//#pragma pack() --> 设置默认对齐数
//#pragma pack(1)
//struct S1
//{
//	char c1;
//	int i;
//	char c2;
//};
//#pragma pack()//将对齐数4重新改回默认对齐数8
//
//int main()
//{
//	printf("%d\n", sizeof(struct S1));//6
//	return 0;
//}

//结构体传参
//结构体传参的时候，尽量传结构体的地址

//位段
// 位段 - 位 - 二进制位
//位段的声明和结构是类似的，有两个不同：
//1. 位段的成员必须是 int、unsigned int 或signed int （可以是char），在C99中位段成员的类型也可以选择其他类型。
//2. 位段的成员名后边有⼀个冒号和⼀个数字。

//A就是⼀个位段类型。那位段A所占内存的⼤⼩是多少？
//struct A
//{
//	int _a : 2;
//	int _b : 5;
//	int _c : 10;
//	int _d : 30;
//};
//int main()
//{
//	printf("%d\n", sizeof(struct A));//8
//	return 0;
//}

//位段的内存分配
//1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
//2. 位段的空间上是按照需要以4个字节（ int ）或者1个字节（ char ）的⽅式来开辟的。
//3. 位段涉及很多不确定因素，位段是不跨平台的，注重可移植的程序应该避免使⽤位段。
//⼀个例⼦ 
//struct S
//{
//	char a : 3;   //01100010（0x62）
//	char b : 4;   //00000011（0x03）            ----- > 3个字节
//	char c : 5;   //00000100（0x04）
//	char d : 4;
//};
//int main()
//{
//	struct S s = { 0 };
//	s.a = 10;// 1010
//	s.b = 12;// 1100
//	s.c = 3;//  0011
//	s.d = 4;//  0100
//
//	printf("%d\n", sizeof(struct S));//3
//	return 0;
//}
//空间是如何开辟的？ 

//位段使用的注意事项
//位段的⼏个成员共有同⼀个字节，这样有些成员的起始位置并不是某个字节的起始位置，那么这些位
//置处是没有地址的。内存中每个字节分配⼀个地址，⼀个字节内部的bit位是没有地址的。
//所以不能对位段的成员使⽤& 操作符，这样就不能使⽤scanf直接给位段的成员输⼊值，只能是先输⼊
//放在⼀个变量中，然后赋值给位段的成员。
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	//scanf("%d", &sa._b);//这是错误的 

	//正确的⽰范 
	int b = 0;
	scanf("%d", &b);
	sa._b = b;

	return 0;
}
